#include "VeinWalker.h"
#include "Image.h"
#include "Math.h"
#include "Averagizer.h"
#include <iostream>
#include "Screen.h"
#include "JunctionFinder.h"
#include "Debug.h"

VeinWalker::VeinWalker(const Image *pImage, F64 nHintRow, F64 nHintCol, F64 nHintAngle,
                       const Criteria *pCriteria) :
	ViewBW(pImage, nHintRow, nHintCol, nHintAngle),
	m_crit(*pCriteria)
{
	Detect(INFINITY);
}

/*static*/ void VeinWalker::Detect(F64 nAngleForHorizon) {
	/* we run horizontally and get the length of the white line */
	U32 nMinLen = 0xFFFFFFFF;
	F64 nMinLenAngle = 0;
	U32 nMaxLen = 0;
	F64 nMaxLenAngle = 0;
	F64 nAngle;
	F64 nHintAngle = GetCenterAngle();
	
	if (nAngleForHorizon != INFINITY && (nAngleForHorizon < 0.0 || nAngleForHorizon >= 360.0))
		abort(); 
	if (m_crit.m_nMaxAngleDist >= 89.0) // messes up nAngleLow and nAngleHigh too much
		abort();
	
	if (!m_crit.m_bFailDueToAngleOutOfRange && nAngleForHorizon != INFINITY && m_crit.m_bDetectByMin) 
		abort(); // this is not supported yet
	m_nHorizon = 0;
/*	// ORIDEBUG BEGIN - for new_vein 008
	if (Debug::g_bAtMainVein) {
		Debug::Stream() << "Detect - " << GetRow() << ", " << GetCol() << ", " << GetAngle() << std::endl;
	}
	if (fabs(GetRow() - 104.167) <= .1 && fabs(GetCol() - 161.826) <= .1 && fabs(GetAngle() - 80) <= .1 ||
	    fabs(GetRow() - 104.978) <= .1 && fabs(GetCol() - 160.668) <= .1 && fabs(GetAngle() - 80) <= .1 ||
	    fabs(GetRow() - 105.963) <= .1 && fabs(GetCol() - 160.494) <= .1 && fabs(GetAngle() - 90) <= .1 ||
	    fabs(GetRow() - 106.963) <= .1 && fabs(GetCol() - 159.494) <= .1 && fabs(GetAngle() - 90) <= .1 ||
	    fabs(GetRow() - 107.963) <= .1 && fabs(GetCol() - 159.494) <= .1 && fabs(GetAngle() - 90) <= .1 ||
	    fabs(GetRow() - 108.963) <= .1 && fabs(GetCol() - 159.494) <= .1 && fabs(GetAngle() - 90) <= .1)
		Debug::DumpDebugInfoAndBreak(this);
	// ORIDEBUG END*/
	for(nAngle = 0.0; nAngle < 180.0; nAngle += 10.0) {
		U32 nLen = 0; F64 nCol = 0;
		bool bIsAngleInRange;
		bool bHorizonIsForward;
		F64 nAngleLow = nAngleForHorizon - m_crit.m_nMaxAngleDist;
		F64 nAngleHigh = nAngleForHorizon + m_crit.m_nMaxAngleDist;
		if (nAngleForHorizon == INFINITY) {
			bIsAngleInRange = true;
		} else if (nAngleLow < 0.0) {
			if (nAngleHigh < 180.0) {
				bIsAngleInRange = nAngle > nAngleLow + 180.0 || nAngle < nAngleHigh;
				bHorizonIsForward = nAngle < nAngleHigh;
			} 
			else abort(); 
		} else if (nAngleLow < 180.0) {
			if (nAngleHigh < 180.0) {
				bIsAngleInRange = nAngleLow < nAngle && nAngle < nAngleHigh;
				bHorizonIsForward = true;	
			}
			else if (nAngleHigh < 360.0) {
				bIsAngleInRange = nAngle > nAngleLow || nAngle < nAngleHigh - 180.0;
				bHorizonIsForward = nAngle > nAngleLow;
			}
			else abort();
		} else if (nAngleLow < 360.0) {
			if (nAngleHigh < 360.0) {
				bIsAngleInRange = nAngleLow - 180.0 < nAngle && nAngle < nAngleHigh - 180.0;
				bHorizonIsForward = false;
			} else {
				bIsAngleInRange = nAngle < nAngleHigh - 360.0 || nAngle > nAngleLow - 180.0;
				bHorizonIsForward = nAngle < nAngleHigh - 360.0;
			}
		} else abort();
		
		SetCenterAngle(nAngle);
		while(GetPix(0, nCol++));
		nLen = (U32)nCol;
		if (bIsAngleInRange && bHorizonIsForward && nLen > m_nHorizon)
			m_nHorizon = nLen;
		S32 nLastLen = nLen;
		nCol = -1;
		while(GetPix(0, nCol--)) ++nLen;
		if (bIsAngleInRange && !bHorizonIsForward && nLen - nLastLen + 1 > m_nHorizon)
			m_nHorizon = nLen - nLastLen + 1;
		if (nLen < nMinLen) {
			nMinLenAngle = nAngle;
			nMinLen = nLen;
		}
		// this if may seem confusing, but it is in incorrect
		// we can only fail due to angle being out of range if we check out of range
		if ((m_crit.m_bFailDueToAngleOutOfRange || bIsAngleInRange) && 
		    nLen > nMaxLen) {
			nMaxLenAngle = nAngle;
			nMaxLen = nLen;
		}
	}
	m_nStraightWalkMax = nMaxLen;
	m_nThickness = nMinLen;
	
	if (m_crit.m_bDetectByMin)
		nAngle = Math::NormalizeAngle(nMinLenAngle + 90);
	else
		nAngle = Math::NormalizeAngle(nMaxLenAngle);
	
	if (Math::AngleDist(nHintAngle - nAngle) > 90) 
		nAngle = Math::NormalizeAngle(nAngle + 180);
	
// 	/* when m_bDetectByMin is on, this code can run a bit faster - OBSOLETE COMMENT */
// 	SetCenterAngle(nAngle + 90); - COMMENTED LINES ARE OBSOLETE
// 	F64 nHighCol = 0;
// 	while(GetPix(0, nHighCol++));
// 	F64 nLowCol = -1;
// 	while(GetPix(0, nLowCol--));
// 	F64 nRow = 0;
// 	F64 nCol = (nLowCol + nHighCol) / 2.0;
// 	TransformPoints(&nRow, &nCol);
// 	SetCenterRow(nRow);
// 	SetCenterCol(nCol);
	SetCenterAngle(nAngle);
}

bool VeinWalker::WalkSingle(F64 nHillClimbingStartRow, F64 nHillClimbingStartCol, 
                            const WalkUntilContext *pCtx)
{
	static int s_nDepthForDebug = 0; ++s_nDepthForDebug;
	RunFinder<VeinWalker> rf(this);
	Run r = rf.GetVertRunForPoint(0, 0, 0xFF);
	if (!r.m_nLength) abort();
	std::vector<Run> vecRuns;
	rf.GetAdjacentVertRuns(r, 1, 0xFF, &vecRuns);
	if (!vecRuns.size()) {
		--s_nDepthForDebug;
		return false;
	}
	std::vector<Run>::iterator iter;
	JunctionFinder jf(GetView()->GetSourceImage());
	const ViewState *pState = GetView()->GetViewState();
	JunctionFinder::Criteria critJunction;
	critJunction.m_nMinSize = (S32)m_crit.m_nMaxPeepDist; 
	critJunction.m_nMaxJunctionDistance = 0;
	critJunction.m_bPathAllowedToEnd = true;
	std::vector<JunctionFinder::Info::Node> vec;
	std::vector<JunctionFinder::Info::Node90> vec90;
	
	if (critJunction.m_nMinSize > 0)
		jf.GetJunctionInSingleDirection(pState, &critJunction, &vec, &vec90);
	if (vec90.size()) abort();
	F64 nNextPeepDist;
	if ((nNextPeepDist = Math::PointDist2(nHillClimbingStartRow - GetRow(), 
	                     nHillClimbingStartCol - GetCol())) > Math::Sqr(m_crit.m_nMaxPeepDist))
		nNextPeepDist = 0;
	else
		nNextPeepDist = m_crit.m_nMaxPeepDist - 
		                Math::FloorSqrt((U32)nNextPeepDist);
//	nNextPeepDist = m_crit.m_nMaxPeepDist; // ORIDEBUG
	Point<F64> ptChosen(INFINITY, INFINITY);
	if (vec.size() && !m_crit.m_bNoHillClimbing) {
		Criteria crit = m_crit;
		crit.m_nMaxPeepDist = crit.m_nMaxDist = nNextPeepDist;
		U32 nBestScore = 0; // each path is given a score, this is the best one so far
		U32 nScore; // current score
		for(iter = vecRuns.begin(); iter != vecRuns.end(); ++iter) {
			S32 nCurrentRow;
			VeinWalker vw(this, *iter, &nCurrentRow, &crit);
			Point<F64> ptCandidate = Point<F64>(vw.GetRow(), vw.GetCol());
			StopReason sr;
			U32 nAngleDist;
			{
				Averagizer<> avDummy;
				std::vector<ViewState> vecDummy;
				sr = vw.WalkUntilContinue(&avDummy, &vecDummy, pCtx);
			}
			nAngleDist = abs((S32)Math::AngleOfPoints(vw.GetCol() - GetCol(),
			                                          -(vw.GetRow() - GetRow())) - (S32)GetAngle());
			if (s_nDepthForDebug == 1) {
				static S32 s_nCnt;
				if (iter == vecRuns.begin()) {
					++s_nCnt;
					Debug::Stream() << "ORIDEBUG-FOR-CHILD-VEIN-SPLIT-STOP: O, " 
					                << GetRow() << ", " << GetCol() << ", "
					                << GetAngle() << std::endl;
				}
				Debug::Stream() << "ORIDEBUG-FOR-CHILD-VEIN-SPLIT-STOP: " << s_nCnt << ", " 
				                << vw.GetRow() << ", " << vw.GetCol() << ", "
				                << Math::AngleOfPoints(vw.GetCol() - GetCol(), 
				                                       -(vw.GetRow() - GetRow())) << std::endl;
			}
			if ((nScore = GetPathScore(sr, nAngleDist, nCurrentRow)) > nBestScore) {
				nBestScore = nScore;
				ptChosen = ptCandidate;
			}
		}
		if (ptChosen.Row() == INFINITY) abort();
	} else {
		F64 nChosenRow, nChosenCol;
		nChosenRow = 0;
		while(GetPix(nChosenRow, 0)) {
			if (GetPix(nChosenRow, 1)) goto found_way;
			nChosenRow = nChosenRow <= 0 ? -nChosenRow + 1 : -nChosenRow; 
		}
		if (nChosenRow <= 0) {
			nChosenRow = -nChosenRow + 1;
			while(GetPix(nChosenRow, 0)) {
				if (GetPix(nChosenRow, 1)) goto found_way;
				++nChosenRow;
			}
		} else {
			nChosenRow = -nChosenRow;
			while(GetPix(nChosenRow, 0)) {
				if (GetPix(nChosenRow, 1)) goto found_way;
				--nChosenRow;
			}
		}
		// this code is only reached if we did not goto found_way 
		// (should be, never)
		abort();
	found_way: // this label gets the selected nRow and does proper handling
		nChosenCol = 1.0;
		TransformPoints(&nChosenRow, &nChosenCol);
		ptChosen.Set(nChosenRow, nChosenCol);
	}
/*	{ // ORIDEBUG BEGIN
		if (s_nDepthForDebug == 1) {
			static S32 nMaxDiversion = 0;
			if (fabs(nRow) > nMaxDiversion) {
				nMaxDiversion = (S32)fabs(nRow);
				Debug::Stream() << "nMaxDiversion = " << nMaxDiversion << std::endl;
				if (nMaxDiversion == 13)
					Debug::Break();
			}
		}
	} // ORIDEBUG END*/
	SetCenterRow(ptChosen.Row());
	SetCenterCol(ptChosen.Col());
	Detect(pCtx->m_nFirstOrigAngle);
	--s_nDepthForDebug;
	return true;
}

VeinWalker::VeinWalker(const VeinWalker *pWalkerParent, Run r, S32 *pnSelectedRow, const Criteria *pCrit) :
	ViewBW(pWalkerParent->GetView()->GetSourceImage(), 0, 0, 0),
	m_crit(*pCrit)
{
	F64 nRow, nCol = 1.0;
	if (r.m_nStartPos <= 0 && r.EndPos() >= 0) nRow = 0;
	else if (r.m_nStartPos <= 0 && r.EndPos() <= 0) nRow = r.EndPos();
	else if (r.m_nStartPos >= 0 && r.EndPos() >= 0) nRow = r.m_nStartPos;
	else abort();
	*pnSelectedRow = (S32)nRow; // we must do this BEFORE the Transform
	pWalkerParent->TransformPoints(&nRow, &nCol);
	SetCenterRow(nRow);
	SetCenterCol(nCol);
	SetCenterAngle(pWalkerParent->GetAngle()); // we DO NOT use Detect()
}

U32 VeinWalker::GetPathScore(StopReason sr, U32 nAngleDist, S32 nRow)
{
	U32 nRet = 0x80000000;
	nRet += sr.OnlyFar() ? 5000000 : 0;
	nRet -= nAngleDist * 3;
	nRet -= abs(nRow);
	return nRet;
}

VeinWalker::StopReason VeinWalker::WalkUntil(Averagizer<> *pAvg, std::vector<ViewState> *pVec)
{
	WalkUntilContext ctx(NULL, GetAngle());
	return WalkUntilContinue(pAvg, pVec, &ctx);
}

VeinWalker::StopReason VeinWalker::WalkUntilContinue(Averagizer<> *pAvg, std::vector<ViewState> *pVec_,
                                                     const WalkUntilContext *pContextPrev)
{
	WalkUntilContext ctx = *pContextPrev;
	ctx.m_pVec = pVec_;
	F64 nOrigCol = GetCol();
	F64 nOrigRow = GetRow();
	F64 nMaxSqDist = m_crit.m_nMaxDist * m_crit.m_nMaxDist;
	bool bCanWalk = true, bNoTurn = true, bNoFar = true, bNoIteratedEnough = true,
	     bNoLostHorizon = true, bNoLooped = true, bNoDiverted = true;
	pAvg->Add(GetThickness());
	ctx.m_pVec->push_back(ViewState(GetRow(), GetCol(), GetAngle()));
	Averagizer<> avgThicknessBeforeHorizonless;
	while(  (bCanWalk = WalkSingle(!pContextPrev->m_pVec ? GetRow() : nOrigRow, 
	                               !pContextPrev->m_pVec ? GetCol() : nOrigCol, &ctx)) && 
	        (bNoTurn = Math::AngleDist(ctx.m_nFirstOrigAngle - GetAngle()) < m_crit.m_nMaxAngleDist) && 
	        (bNoFar = Math::PointDist2(nOrigRow - GetRow(), nOrigCol - GetCol()) < nMaxSqDist) &&
	        (bNoIteratedEnough = (ctx.m_nIterations++ != m_crit.m_nMaxIterations)) &&
	        (bNoLostHorizon = CountHorizonlessWalks(&ctx.m_nHorizonlessIteraions, pAvg, 
	                                                &avgThicknessBeforeHorizonless)) &&
	        (bNoLooped = PreventLoop(ctx.m_pVec)) &&
	        (bNoDiverted = PreventDiversion(ctx.m_nFirstOrigAngle, ctx.m_pVec, pContextPrev->m_pVec)) 
	        )  {
		pAvg->Add(GetThickness());
		ctx.m_pVec->push_back(ViewState(GetRow(), GetCol(), GetAngle()));
	}
#if defined(DEBUG_STOP_AT_WALKUNTIL_END) && DEBUG_STOP_AT_WALKUNTIL_END
	if (!pContextPrev->m_pVec && (DEBUG_STOP_AT_WALKUNTIL_END != 2 || Debug::g_bAtMainVein))
		Debug::DumpDebugInfoAndBreak(this);
#endif
	// if we were in a horizonless state, we cut all horizonless states 
	// at the end of the state vector
	
	StopReason ret;
	if (!bCanWalk) ret.SetCanWalk();
	if (!bNoFar) ret.SetFar();
	if (!bNoIteratedEnough) ret.SetIteratedEnough();
	if (!bNoLostHorizon) ret.SetLostHorizon();
	if (!bNoTurn) {
		if (!m_crit.m_bFailDueToAngleOutOfRange)
			abort();
		ret.SetTurn();
	}
	if (!bNoLooped) ret.SetLooped();
	if (!bNoDiverted) ret.SetDiverted();
	
//	if (!bNoLostHorizon)
//		--nHorizonlessIterations;
//	if (nHorizonlessIterations) 
//		this->SetViewState(&(*pVec)[pVec->size() - nHorizonlessIterations]);
//	for(U32 i = 0; i < nHorizonlessIterations; ++i) pVec->pop_back();
//	if (avgThicknessBeforeHorizonless.Count()) {
//		if (!avgThicknessBeforeHorizonless.Count()) abort();
//		*pAvg = avgThicknessBeforeHorizonless;
//	}
	
	if (bCanWalk && bNoFar && bNoIteratedEnough && bNoTurn && bNoLostHorizon && bNoLooped && bNoDiverted) 
		abort();
	return ret;
#undef OUR_LOOP
}

bool VeinWalker::CountHorizonlessWalks(U32 *pnHorizonlessIterations,
                                       const Averagizer<> *pAvg,
                                       Averagizer<> *pAvgThicknessBeforeHorizonless)
{
/*	// ORIDEBUG BEGIN - for new_vein 008
	if (Debug::g_bAtMainVein)
		Debug::Stream() << "CountHorizonlessWalks: iterations: " << *pnHorizonlessIterations << ", "
				<< "horizon: " << m_nHorizon << std::endl;
	// ORIDEBUG END*/
	if (m_nHorizon < m_crit.m_nMinHorizonDist)
	{
		if (!*pnHorizonlessIterations)
			*pAvgThicknessBeforeHorizonless = *pAvg;
		++*pnHorizonlessIterations;
	}
	else
		*pnHorizonlessIterations = 0;
	return *pnHorizonlessIterations <= m_crit.m_nMaxAllowedHorizonlessWalks;
}

bool VeinWalker::PreventLoop(std::vector<ViewState> *pVec)
{
	if (m_crit.m_nLoopPreventEvery == 0) abort();
	F64 nLoopPreventionDistanceSqr = Math::Sqr(m_crit.m_nLoopPreventionDistance);
	
	if (pVec->size() > 0 && pVec->size() % m_crit.m_nLoopPreventEvery == 0) {
		F64 nSqrDist = 
		   Math::PointDist2(GetRow() - (*pVec)[pVec->size() - m_crit.m_nLoopPreventEvery].m_nCenterRow,
		                   GetCol() - (*pVec)[pVec->size() - m_crit.m_nLoopPreventEvery].m_nCenterCol);
		return (nSqrDist >= nLoopPreventionDistanceSqr);
	}
	return true;
} 

bool VeinWalker::PreventDiversion(F64 nAngle, const std::vector<ViewState> *pVec, 
                                  const std::vector<ViewState> *pPrevVec)
{
	U32 nPrevVecSize = pPrevVec ? pPrevVec->size() : 0;
	if (!m_crit.m_nSegmentForAngleCheck)
		return true;
	if (pVec->size() + nPrevVecSize < m_crit.m_nSegmentForAngleCheck)
		return true; 
	const ViewState *pPrevState;
	if (pVec->size() >= m_crit.m_nSegmentForAngleCheck) 
		pPrevState = &(*pVec)[pVec->size() - m_crit.m_nSegmentForAngleCheck];
	else
		pPrevState = &(*pPrevVec)[pPrevVec->size() + pVec->size() - m_crit.m_nSegmentForAngleCheck];
		
	F64 nDistHorz, nDistVert;
	nDistHorz = GetCol() - pPrevState->m_nCenterCol;
	nDistVert = GetRow() - pPrevState->m_nCenterRow;
	nDistVert = -nDistVert; /* trigonometric coordinates are different than matrix ones */
	F64 nAngleOfSegment = Math::AngleOfPoints(nDistHorz, nDistVert);
	return Math::AngleDist(nAngle - nAngleOfSegment) < m_crit.m_nMaxAngleDist;
}

void VeinWalker::GetVeinInfo(bool bUniDirectional, Info *pVi)
{
	Averagizer<> avg;
	F64 nCol = GetCol();
	F64 nRow = GetRow();
	F64 nAngle = GetAngle();	
	pVi->m_vecStates1.clear();
#if DEBUG_STOP_AT_CHANGE_AT_1ST_DIRECTION_FROM_NO_HC || \
    DEBUG_STOP_AT_CHANGE_AT_2ND_DIRECTION_FROM_NO_HC
	extern bool Debug::g_bGetVeinInfoFirstDirection;
	Debug::g_bGetVeinInfoFirstDirection = true;
#endif
	WalkUntil(&avg, &pVi->m_vecStates1);
	pVi->m_p1.Set(GetRow(), GetCol());
	if (!bUniDirectional) {
		SetCenterRow(nRow);
		SetCenterCol(nCol);
		SetCenterAngle(nAngle + 180);
		pVi->m_vecStates2.clear();
#if DEBUG_STOP_AT_CHANGE_AT_1ST_DIRECTION_FROM_NO_HC || \
    DEBUG_STOP_AT_CHANGE_AT_2ND_DIRECTION_FROM_NO_HC
	Debug::g_bGetVeinInfoFirstDirection = false;
#endif
		WalkUntil(&avg, &pVi->m_vecStates2);
		pVi->m_p2.Set(GetRow(), GetCol());
		SetCenterRow(nRow);
		SetCenterCol(nCol);
		SetCenterAngle(nAngle + 180);
	}
	pVi->m_nAverageThickness = avg.Get();
}

